// ============================================ // CONFIGURAÇÃO GERAL // ============================================ var ID_PLANILHA = '1u1OESExWB_noM0YUCIi6XhnzmshpeFtxMzCjbFSNNi4'; var ID_FORMULARIO = '17GvJnG3-V578Gv69KRLPvSqBMxJefuCOIx2P65E9qFU'; var IDPASTAFOTOS = '1cQbwXZb4UPfRW2k3Ltjvmp_vItBmDinFraw4T-h0s_h0PuAqz0ATl710-jkV06LBkdsIF1wI'; var NOME_ABA = 'CADASTRO'; const FORM_ID = ID_FORMULARIO; const COLUNA_TAG = 3; const TITULO_PERGUNTA_TAG = 'Código da TAG'; var LINK_EDICAO = 'https://apptagemg.resinadosdf.com.br/gerenciar'; var LINK_PUBLICO_BASE = 'https://apptagemg.resinadosdf.com.br/pagpublica.html?tag='; // ============================================ // MAPA DE COLUNAS DA ABA CADASTRO (0-based) // ============================================ var IDX = { timestamp : 0, email : 1, tag : 2, nome : 3, telefone : 4, dataNasc : 5, tipoSang : 6, doadorOrgaos : 7, meds : 8, doencas : 9, alergias : 10, plano : 11, sus : 12, cont1Nome : 13, cont1Tel : 14, cont2Nome : 15, cont2Tel : 16, obs : 17, foto : 18, apelido_tag : 19, categoria_tag : 20, status_tag : 21, pet_nome : 22, pet_especie : 23, pet_raca : 24, pet_pelagem : 25, pet_sexo : 26, pet_castrado : 27, pet_nascimento : 28, pet_vacinas : 29, pet_saude : 30, pet_meds : 31, pet_procedimentos: 32, pet_vet_nome : 33, pet_vet_tel : 34, pet_vet_endereco : 35, pet_localizacao : 36, pet_exames : 37, pet_obs : 38, tutor_cont1_nome : 39, tutor_cont1_tel : 40, tutor_cont2_nome : 41, tutor_cont2_tel : 42, pet_vet_lat : 43, pet_vet_lng : 44 }; // ============================================ // AUXILIARES // ============================================ function getSpreadsheet() { return SpreadsheetApp.openById(ID_PLANILHA); } function getCadastroSheet() { var ss = getSpreadsheet(); var sheet = ss.getSheetByName(NOME_ABA); if (!sheet) throw new Error('Aba não encontrada: ' + NOME_ABA); return sheet; } function normalizeStr(v) { return String(v || '').trim(); } function normalizeEmail(v) { return normalizeStr(v).toLowerCase(); } function normalizeTag(v) { return normalizeStr(v).replace(/^'+/, ''); } function jsonResponse(obj) { return ContentService .createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); } function escapeHtml(text) { text = String(text || ''); return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatDateToISO(dateValue) { if (!(dateValue instanceof Date)) return String(dateValue || ''); return dateValue.getFullYear() + '-' + String(dateValue.getMonth() + 1).padStart(2, '0') + '-' + String(dateValue.getDate()).padStart(2, '0'); } function formatDateToBR(dateValue) { if (!(dateValue instanceof Date)) return String(dateValue || ''); return String(dateValue.getDate()).padStart(2, '0') + '/' + String(dateValue.getMonth() + 1).padStart(2, '0') + '/' + dateValue.getFullYear(); } function extrairIdDrive(url) { url = String(url || ''); var m1 = url.match(/[?&]id=([a-zA-Z0-9_-]+)/); if (m1 && m1[1]) return m1[1]; var m2 = url.match(/\/d\/([a-zA-Z0-9_-]+)/); if (m2 && m2[1]) return m2[1]; return ''; } function thumbDrive(url, size) { var id = extrairIdDrive(url); if (!id) return String(url || ''); return 'https://drive.google.com/thumbnail?id=' + id + '&sz=' + (size || 's600'); } function gerarCodigoVerificacao() { var num = Math.floor(Math.random() * 1000000); return ('000000' + num).slice(-6); } function montarChaveCodigoConta(email) { return 'OTP_CONTA_' + normalizeEmail(email); } function montarChaveCodigoTag(email, tag) { return 'OTP_TAG_' + normalizeEmail(email) + '_' + normalizeTag(tag); } function normalizarCategoria(valor) { var v = normalizeStr(valor).toLowerCase(); if (v === 'petmacho') return 'pet_macho'; if (v === 'petfemea') return 'pet_femea'; if (v === 'pet_femea') return 'pet_femea'; if (v === 'pet_macho') return 'pet_macho'; return 'emergencial'; } function normalizarStatus(valor) { var v = normalizeStr(valor).toUpperCase(); if (v === 'PERDIDA') return 'PERDIDA'; if (v === 'ARQUIVADA') return 'ARQUIVADA'; return 'ATIVA'; } function categoriaEhPet(cat) { cat = normalizarCategoria(cat); return cat === 'pet_macho' || cat === 'pet_femea'; } function garantirCabecalhoCadastro() { var sheet = getCadastroSheet(); var headers = [ 'timestamp','email','tag','nome','telefone','dataNasc','tipoSang','doadorOrgaos','meds','doencas', 'alergias','plano','sus','cont1Nome','cont1Tel','cont2Nome','cont2Tel','obs','foto', 'apelido_tag','categoria_tag','status_tag', 'pet_nome','pet_especie','pet_raca','pet_pelagem','pet_sexo','pet_castrado','pet_nascimento','pet_vacinas', 'pet_saude','pet_meds','pet_procedimentos','pet_vet_nome','pet_vet_tel','pet_vet_endereco','pet_localizacao', 'pet_exames','pet_obs','tutor_cont1_nome','tutor_cont1_tel','tutor_cont2_nome','tutor_cont2_tel', 'pet_vet_lat','pet_vet_lng' ]; var range = sheet.getRange(1, 1, 1, headers.length); var atual = range.getValues()[0]; var precisa = false; for (var i = 0; i < headers.length; i++) { if (String(atual[i] || '') !== headers[i]) { precisa = true; break; } } if (precisa) { range.setValues([headers]); sheet.setFrozenRows(1); } } function uploadFotoParaDrive(base64Data, tag, nome) { var token = ScriptApp.getOAuthToken(); var mimeType = 'image/jpeg'; var limpo = String(base64Data || ''); if (limpo.indexOf('base64,') > -1) limpo = limpo.split('base64,')[1]; var metadata = { name: normalizeTag(tag) + '_' + String(nome || '').replace(/[^a-zA-Z0-9]/g, '_').substring(0, 20) + '.jpg', parents: [IDPASTAFOTOS] }; var boundary = '-------TAG314159265'; var body = '--' + boundary + '\r\n' + 'Content-Type: application/json; charset=UTF-8\r\n\r\n' + JSON.stringify(metadata) + '\r\n' + '--' + boundary + '\r\n' + 'Content-Type: ' + mimeType + '\r\n' + 'Content-Transfer-Encoding: base64\r\n\r\n' + limpo + '\r\n' + '--' + boundary + '--'; var response = UrlFetchApp.fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id', { method: 'POST', contentType: 'multipart/related; boundary="' + boundary + '"', headers: { Authorization: 'Bearer ' + token }, payload: body, muteHttpExceptions: true }); var result = JSON.parse(response.getContentText()); if (!result.id) throw new Error('Upload falhou: ' + response.getContentText()); UrlFetchApp.fetch('https://www.googleapis.com/drive/v3/files/' + result.id + '/permissions', { method: 'POST', contentType: 'application/json', headers: { Authorization: 'Bearer ' + token }, payload: JSON.stringify({ role: 'reader', type: 'anyone' }), muteHttpExceptions: true }); return 'https://drive.google.com/thumbnail?id=' + result.id + '&sz=s600'; } // ============================================ // ROTAS // ============================================ function doGet(e) { var params = (e && e.parameter) ? e.parameter : {}; var action = params.action; var gerenciar = params.gerenciar; var tag = params.tag; var index = params.index; if (action === 'buscar') return buscarTag(e); if (index === '1') { return HtmlService.createHtmlOutputFromFile('index') .setTitle('TAG Emergencial') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) .addMetaTag('viewport', 'width=device-width, initial-scale=1'); } if (gerenciar === '1') { return HtmlService.createHtmlOutputFromFile('GerenciarTAG') .setTitle('Gerenciar TAG - TAG Emergencial') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) .addMetaTag('viewport', 'width=device-width, initial-scale=1'); } if (tag) { return HtmlService.createHtmlOutputFromFile('pagpublica') .setTitle('TAG ' + tag + ' - TAG Emergencial') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) .addMetaTag('viewport', 'width=device-width, initial-scale=1'); } return HtmlService.createHtmlOutputFromFile('pagpublica') .setTitle('TAG Emergencial') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) .addMetaTag('viewport', 'width=device-width, initial-scale=1'); } function doPost(e) { try { garantirCabecalhoCadastro(); var body = JSON.parse((e && e.postData && e.postData.contents) ? e.postData.contents : '{}'); var action = body.action; if (action === 'verificarTagExistente') return jsonResponse({ existe: verificarTagExistente(body.tag) }); if (action === 'receberCadastroWebApp') return jsonResponse(receberCadastroWebApp(body)); if (action === 'validarAcesso') return jsonResponse(validarAcesso(body.email, body.tag)); if (action === 'validarCodigo') return jsonResponse(validarCodigo(body.email, body.tag, body.codigoDigitado)); if (action === 'buscarDadosTAG') return jsonResponse(buscarDadosTAG(body.email, body.tag)); if (action === 'atualizarDadosTAG') return jsonResponse(atualizarDadosTAG(body)); if (action === 'enviarCodigoAcessoConta') return jsonResponse(enviarCodigoAcessoConta(body.email)); if (action === 'validarCodigoAcessoConta') return jsonResponse(validarCodigoAcessoConta(body.email, body.codigoDigitado)); if (action === 'listarTagsPorEmail') return jsonResponse(listarTagsPorEmail(body.email)); if (action === 'buscarDadosTagPorLinha') return jsonResponse(buscarDadosTagPorLinha(body.email, body.numeroLinha)); if (action === 'atualizarMetaTag') return jsonResponse(atualizarMetaTag(body)); if (action === 'uploadFoto') { var url = uploadFotoParaDrive(body.fotoBase64, body.tag, body.nome); return jsonResponse({ sucesso: true, url: url }); } return jsonResponse({ erro: 'Ação não reconhecida: ' + action }); } catch (err) { return jsonResponse({ erro: 'Erro interno: ' + err.toString() }); } } // ============================================ // CONSULTA PÚBLICA // ============================================ function buscarTag(e) { try { var tag = normalizeTag(e && e.parameter && e.parameter.tag ? e.parameter.tag : ''); if (!tag) { return ContentService.createTextOutput(JSON.stringify({ erro: 'TAG não informada.' })) .setMimeType(ContentService.MimeType.JSON); } var sheet = getCadastroSheet(); var dados = sheet.getDataRange().getValues(); for (var i = 1; i < dados.length; i++) { if (normalizeTag(dados[i][IDX.tag]) !== tag) continue; var linha = dados[i]; var fotoThumb = thumbDrive(linha[IDX.foto], 's400'); var resultado = { tag: String(linha[IDX.tag] || ''), categoria: normalizarCategoria(linha[IDX.categoria_tag] || 'emergencial'), status: normalizarStatus(linha[IDX.status_tag] || 'ATIVA'), apelido: String(linha[IDX.apelido_tag] || '').trim(), nome: String(linha[IDX.nome] || ''), cpf: String(linha[IDX.telefone] || ''), nascimento: formatDateToBR(linha[IDX.dataNasc]), sangue: String(linha[IDX.tipoSang] || ''), doadorOrgaos: String(linha[IDX.doadorOrgaos] || ''), meds: String(linha[IDX.meds] || ''), doencas: String(linha[IDX.doencas] || ''), alergias: String(linha[IDX.alergias] || ''), plano: String(linha[IDX.plano] || ''), planoNum: String(linha[IDX.sus] || ''), cont1Nome: String(linha[IDX.cont1Nome] || ''), cont1Tel: String(linha[IDX.cont1Tel] || ''), cont2Nome: String(linha[IDX.cont2Nome] || ''), cont2Tel: String(linha[IDX.cont2Tel] || ''), obs: String(linha[IDX.obs] || ''), foto: fotoThumb, fotoLink: fotoThumb, petnome: String(linha[IDX.pet_nome] || ''), petespecie: String(linha[IDX.pet_especie] || ''), petraca: String(linha[IDX.pet_raca] || ''), petpelagem: String(linha[IDX.pet_pelagem] || ''), petsexo: String(linha[IDX.pet_sexo] || ''), petcastrado: String(linha[IDX.pet_castrado] || ''), petnascimento: formatDateToISO(linha[IDX.pet_nascimento]), petvacinas: String(linha[IDX.pet_vacinas] || ''), petsaude: String(linha[IDX.pet_saude] || ''), petmeds: String(linha[IDX.pet_meds] || ''), petprocedimentos: String(linha[IDX.pet_procedimentos] || ''), petvetnome: String(linha[IDX.pet_vet_nome] || ''), petvettel: String(linha[IDX.pet_vet_tel] || ''), petvetendereco: String(linha[IDX.pet_vet_endereco] || ''), petlocalizacao: String(linha[IDX.pet_localizacao] || ''), petexames: String(linha[IDX.pet_exames] || ''), petobs: String(linha[IDX.pet_obs] || ''), tutorcont1nome: String(linha[IDX.tutor_cont1_nome] || ''), tutorcont1tel: String(linha[IDX.tutor_cont1_tel] || ''), tutorcont2nome: String(linha[IDX.tutor_cont2_nome] || ''), tutorcont2tel: String(linha[IDX.tutor_cont2_tel] || ''), petvetlat: String(linha[IDX.pet_vet_lat] || ''), petvetlng: String(linha[IDX.pet_vet_lng] || '') }; return ContentService.createTextOutput(JSON.stringify(resultado)) .setMimeType(ContentService.MimeType.JSON); } return ContentService.createTextOutput(JSON.stringify({ erro: 'TAG não encontrada.' })) .setMimeType(ContentService.MimeType.JSON); } catch (err) { return ContentService.createTextOutput(JSON.stringify({ erro: err.toString() })) .setMimeType(ContentService.MimeType.JSON); } } // ============================================ // CADASTRO INICIAL // ============================================ function verificarTagExistente(tag) { try { var sheet = getCadastroSheet(); var dados = sheet.getDataRange().getValues(); tag = normalizeTag(tag); for (var i = 1; i < dados.length; i++) { if (normalizeTag(dados[i][IDX.tag]) === tag) return true; } return false; } catch (e) { return false; } } function receberCadastroWebApp(dados) { try { garantirCabecalhoCadastro(); if (!dados.email || !dados.tag || !dados.nome) { return { sucesso: false, erro: 'Incompleto.' }; } if (verificarTagExistente(dados.tag)) { return { sucesso: false, erro: 'TAG em uso.' }; } var linkFoto = ''; if (dados.fotoLink && String(dados.fotoLink).startsWith('data:image')) { linkFoto = uploadFotoParaDrive(dados.fotoLink, dados.tag, dados.nome); } else if (dados.fotoLink && String(dados.fotoLink).startsWith('https://')) { linkFoto = dados.fotoLink; } var sheet = getCadastroSheet(); var row = new Array(IDX.pet_vet_lng + 1).fill(''); row[IDX.timestamp] = new Date(); row[IDX.email] = dados.email || ''; row[IDX.tag] = dados.tag || ''; row[IDX.nome] = dados.nome || ''; row[IDX.telefone] = dados.telefone || ''; row[IDX.dataNasc] = dados.dataNasc || ''; row[IDX.tipoSang] = dados.tipoSang || ''; row[IDX.doadorOrgaos] = dados.doadorOrgaos || ''; row[IDX.meds] = dados.meds || ''; row[IDX.doencas] = dados.doencas || ''; row[IDX.alergias] = dados.alergias || ''; row[IDX.plano] = dados.plano || ''; row[IDX.sus] = dados.sus || ''; row[IDX.cont1Nome] = dados.cont1Nome || ''; row[IDX.cont1Tel] = dados.cont1Tel || ''; row[IDX.cont2Nome] = dados.cont2Nome || ''; row[IDX.cont2Tel] = dados.cont2Tel || ''; row[IDX.obs] = dados.obs || ''; row[IDX.foto] = linkFoto || ''; row[IDX.apelido_tag] = dados.apelido || ''; row[IDX.categoria_tag] = normalizarCategoria(dados.categoria || 'emergencial'); row[IDX.status_tag] = normalizarStatus(dados.status || 'ATIVA'); sheet.appendRow(row); enviarEmailCadastro(dados); return { sucesso: true }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } function enviarEmailCadastro(dados) { var linkPublico = LINK_PUBLICO_BASE + encodeURIComponent(dados.tag || ''); var assunto = '✅ TAG ' + (dados.tag || '') + ' - Cadastro Confirmado | ResinadosDF'; var corpo = 'Olá ' + (dados.nome || '') + ',\n\n' + 'Seu cadastro da TAG ' + (dados.tag || '') + ' foi recebido com sucesso!\n\n' + 'Gerenciar seus dados: ' + LINK_EDICAO + '\n' + 'Link público da sua TAG: ' + linkPublico + '\n\n' + 'Dicas importantes:\n' + '- Guarde este e-mail com o código da sua TAG: ' + (dados.tag || '') + '\n' + '- Mantenha seus dados sempre atualizados\n' + '- Em caso de emergência, o socorrista escaneará o QR Code\n\n' + 'Dúvidas? Responda este e-mail.\n\n' + 'Atenciosamente,\n' + 'Equipe ResinadosDF\n' + 'TAG Emergencial - Sua segurança em primeiro lugar'; var corpoHTML = "
" + "

Olá " + escapeHtml(dados.nome) + ",

" + "

✅ Seu cadastro da TAG Emergencial " + escapeHtml(dados.tag) + " foi recebido com sucesso!

" + "
" + "

" + "

📝 GERENCIAR SEUS DADOS

" + "

Você pode editar suas informações, atualizar contatos de emergência ou alterar seus dados médicos a qualquer momento.

" + "
" + "👉 Clique aqui para gerenciar sua TAG" + "
" + "

Basta informar seu e-mail e o código da TAG para acessar.

" + "
" + "

🔗 LINK PÚBLICO DA SUA TAG

" + "

Este é o link que será aberto quando alguém escanear seu QR Code:

" + "
" + "👁️ Ver minha TAG pública" + "
" + "
" + "

💡 DICAS IMPORTANTES

" + "

• Guarde este e-mail com o código da sua TAG: " + escapeHtml(dados.tag) + "

" + "

• Mantenha seus dados sempre atualizados

" + "

• Em caso de emergência, o socorrista escaneará o QR Code

" + "
" + "

Dúvidas? Responda este e-mail.

" + "

Atenciosamente,

" + "

Equipe ResinadosDF
TAG Emergencial - Sua segurança em primeiro lugar

" + "
"; MailApp.sendEmail({ to: dados.email, subject: assunto, body: corpo, htmlBody: corpoHTML, name: 'Resinados DF' }); } // ============================================ // LOGIN ANTIGO POR TAG // ============================================ function validarAcesso(email, tag) { try { email = normalizeEmail(email); tag = normalizeTag(tag); if (!email || !tag) return { valido: false, erro: 'Informe e-mail e TAG.' }; var sheet = getCadastroSheet(); var dados = sheet.getDataRange().getValues(); var linhaEncontrada = null; for (var i = 1; i < dados.length; i++) { var emailPlanilha = normalizeEmail(dados[i][IDX.email]); var tagPlanilha = normalizeTag(dados[i][IDX.tag]); if (tagPlanilha === tag) { linhaEncontrada = dados[i]; if (emailPlanilha !== email) { return { valido: false, erro: 'O e-mail não confere com o cadastrado para esta TAG.' }; } break; } } if (!linhaEncontrada) return { valido: false, erro: 'TAG não encontrada. Verifique o código digitado.' }; var codigo = gerarCodigoVerificacao(); CacheService.getScriptCache().put(montarChaveCodigoTag(email, tag), codigo, 600); enviarEmailCodigoVerificacao(email, tag, codigo); return { valido: true, mensagem: 'Enviamos um código de verificação para seu e-mail.' }; } catch (er) { return { valido: false, erro: 'Erro ao validar acesso: ' + er.toString() }; } } function validarCodigo(email, tag, codigoDigitado) { try { email = normalizeEmail(email); tag = normalizeTag(tag); codigoDigitado = normalizeStr(codigoDigitado); if (!email || !tag || !codigoDigitado) { return { valido: false, mensagem: 'Informe e-mail, TAG e o código recebido.' }; } var chave = montarChaveCodigoTag(email, tag); var codigoSalvo = CacheService.getScriptCache().get(chave); if (!codigoSalvo) return { valido: false, mensagem: 'Código expirado ou não encontrado.' }; if (codigoSalvo !== codigoDigitado) return { valido: false, mensagem: 'Código inválido.' }; CacheService.getScriptCache().remove(chave); return { valido: true, mensagem: 'Código validado com sucesso.' }; } catch (er) { return { valido: false, mensagem: 'Erro ao validar código: ' + er.toString() }; } } function enviarEmailCodigoVerificacao(email, tag, codigo) { var assunto = 'Código de verificação | TAG Emergencial'; var corpoTexto = 'Olá,\n\n' + 'Você solicitou acesso à sua TAG.\n\n' + 'Código da TAG: ' + tag + '\n' + 'Código de verificação: ' + codigo + '\n\n' + 'Válido por 10 minutos.'; var corpoHtml = '
' + '

Olá,

' + '

Você solicitou acesso à sua TAG.

' + '

Código da TAG: ' + escapeHtml(tag) + '

' + '

' + escapeHtml(codigo) + '

' + '

Este código é válido por 10 minutos.

' + '
'; MailApp.sendEmail({ to: email, subject: assunto, body: corpoTexto, htmlBody: corpoHtml, name: 'Resinados DF' }); } function buscarDadosTAG(email, tag) { try { email = normalizeEmail(email); tag = normalizeTag(tag); var sheet = getCadastroSheet(); var dados = sheet.getDataRange().getValues(); for (var i = 1; i < dados.length; i++) { if (normalizeEmail(dados[i][IDX.email]) === email && normalizeTag(dados[i][IDX.tag]) === tag) { return montarObjetoEdicao(dados[i], i + 1); } } return { erro: 'Não encontrado.' }; } catch (e) { return { erro: e.toString() }; } } // ============================================ // LOGIN POR CONTA / PAINEL MULTI-TAG // ============================================ function enviarCodigoAcessoConta(email) { try { email = normalizeEmail(email); if (!email) return { sucesso: false, erro: 'Informe o e-mail.' }; var tags = listarTagsPorEmail(email); if (!tags.sucesso || !tags.tags.length) { return { sucesso: false, erro: 'Nenhuma TAG encontrada para este e-mail.' }; } var codigo = gerarCodigoVerificacao(); CacheService.getScriptCache().put(montarChaveCodigoConta(email), codigo, 600); var nome = tags.tags[0].nomeTitular || 'cliente'; var assunto = 'Código de acesso ao painel | TAG Emergencial'; var corpoTexto = 'Olá, ' + nome + '!\n\n' + 'Seu código de acesso ao painel é:\n\n' + codigo + '\n\n' + 'Válido por 10 minutos.'; var corpoHtml = '
' + '

Olá, ' + escapeHtml(nome) + '

' + '

Seu código de acesso ao painel é:

' + '

' + escapeHtml(codigo) + '

' + '

Válido por 10 minutos.

' + '
'; MailApp.sendEmail({ to: email, subject: assunto, body: corpoTexto, htmlBody: corpoHtml, name: 'Resinados DF' }); return { sucesso: true, mensagem: 'Código enviado com sucesso.' }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } function validarCodigoAcessoConta(email, codigoDigitado) { try { email = normalizeEmail(email); codigoDigitado = normalizeStr(codigoDigitado); if (!email || !codigoDigitado) { return { sucesso: false, erro: 'Informe e-mail e código.' }; } var chave = montarChaveCodigoConta(email); var codigoSalvo = CacheService.getScriptCache().get(chave); if (!codigoSalvo) return { sucesso: false, erro: 'Código expirado ou não encontrado.' }; if (codigoSalvo !== codigoDigitado) return { sucesso: false, erro: 'Código inválido.' }; CacheService.getScriptCache().remove(chave); return { sucesso: true }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } function listarTagsPorEmail(email) { try { email = normalizeEmail(email); if (!email) return { sucesso: false, erro: 'E-mail não informado.' }; var sheet = getCadastroSheet(); var dados = sheet.getDataRange().getValues(); var tags = []; for (var i = 1; i < dados.length; i++) { if (normalizeEmail(dados[i][IDX.email]) !== email) continue; tags.push({ numeroLinha: i + 1, tag: String(dados[i][IDX.tag] || ''), apelido: String(dados[i][IDX.apelido_tag] || ''), categoria: normalizarCategoria(dados[i][IDX.categoria_tag] || 'emergencial'), status: normalizarStatus(dados[i][IDX.status_tag] || 'ATIVA'), nomeTitular: String(dados[i][IDX.nome] || '') }); } return { sucesso: true, tags: tags }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } function buscarDadosTagPorLinha(email, numeroLinha) { try { email = normalizeEmail(email); numeroLinha = parseInt(numeroLinha, 10); if (!email || !numeroLinha) { return { sucesso: false, erro: 'Dados inválidos.' }; } var sheet = getCadastroSheet(); var lastRow = sheet.getLastRow(); if (numeroLinha < 2 || numeroLinha > lastRow) { return { sucesso: false, erro: 'Linha não encontrada.' }; } var linha = sheet.getRange(numeroLinha, 1, 1, IDX.pet_vet_lng + 1).getValues()[0]; if (normalizeEmail(linha[IDX.email]) !== email) { return { sucesso: false, erro: 'Esta TAG não pertence ao e-mail informado.' }; } return { sucesso: true, dados: montarObjetoEdicao(linha, numeroLinha) }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } function atualizarMetaTag(body) { try { var email = normalizeEmail(body.email); var numeroLinha = parseInt(body.numeroLinha, 10); if (!email || !numeroLinha) return { sucesso: false, erro: 'Dados inválidos.' }; var sheet = getCadastroSheet(); var linha = sheet.getRange(numeroLinha, 1, 1, IDX.pet_vet_lng + 1).getValues()[0]; if (normalizeEmail(linha[IDX.email]) !== email) { return { sucesso: false, erro: 'Esta TAG não pertence a esta conta.' }; } sheet.getRange(numeroLinha, IDX.apelido_tag + 1).setValue(body.apelido || ''); sheet.getRange(numeroLinha, IDX.categoria_tag + 1).setValue(normalizarCategoria(body.categoria || 'emergencial')); sheet.getRange(numeroLinha, IDX.status_tag + 1).setValue(normalizarStatus(body.status || 'ATIVA')); return { sucesso: true, mensagem: 'Dados da TAG atualizados.' }; } catch (e) { return { sucesso: false, erro: e.toString() }; } } // ============================================ // EDIÇÃO COMPLETA // ============================================ function montarObjetoEdicao(linha, numeroLinha) { return { numeroLinha: numeroLinha, email: String(linha[IDX.email] || ''), tag: String(linha[IDX.tag] || ''), nome: String(linha[IDX.nome] || ''), telefone: String(linha[IDX.telefone] || ''), dataNasc: formatDateToISO(linha[IDX.dataNasc]), tipoSang: String(linha[IDX.tipoSang] || ''), doadorOrgaos: String(linha[IDX.doadorOrgaos] || ''), meds: String(linha[IDX.meds] || ''), doencas: String(linha[IDX.doencas] || ''), alergias: String(linha[IDX.alergias] || ''), plano: String(linha[IDX.plano] || ''), sus: String(linha[IDX.sus] || ''), cont1Nome: String(linha[IDX.cont1Nome] || ''), cont1Tel: String(linha[IDX.cont1Tel] || ''), cont2Nome: String(linha[IDX.cont2Nome] || ''), cont2Tel: String(linha[IDX.cont2Tel] || ''), obs: String(linha[IDX.obs] || ''), fotoLink: String(linha[IDX.foto] || ''), fotoThumb: thumbDrive(linha[IDX.foto], 's400'), apelido: String(linha[IDX.apelido_tag] || ''), categoria: normalizarCategoria(linha[IDX.categoria_tag] || 'emergencial'), status: normalizarStatus(linha[IDX.status_tag] || 'ATIVA'), petnome: String(linha[IDX.pet_nome] || ''), petespecie: String(linha[IDX.pet_especie] || ''), petraca: String(linha[IDX.pet_raca] || ''), petpelagem: String(linha[IDX.pet_pelagem] || ''), petsexo: String(linha[IDX.pet_sexo] || ''), petcastrado: String(linha[IDX.pet_castrado] || ''), petnascimento: formatDateToISO(linha[IDX.pet_nascimento]), petvacinas: String(linha[IDX.pet_vacinas] || ''), petsaude: String(linha[IDX.pet_saude] || ''), petmeds: String(linha[IDX.pet_meds] || ''), petprocedimentos: String(linha[IDX.pet_procedimentos] || ''), petvetnome: String(linha[IDX.pet_vet_nome] || ''), petvettel: String(linha[IDX.pet_vet_tel] || ''), petvetendereco: String(linha[IDX.pet_vet_endereco] || ''), petlocalizacao: String(linha[IDX.pet_localizacao] || ''), petexames: String(linha[IDX.pet_exames] || ''), petobs: String(linha[IDX.pet_obs] || ''), tutorcont1nome: String(linha[IDX.tutor_cont1_nome] || ''), tutorcont1tel: String(linha[IDX.tutor_cont1_tel] || ''), tutorcont2nome: String(linha[IDX.tutor_cont2_nome] || ''), tutorcont2tel: String(linha[IDX.tutor_cont2_tel] || ''), petvetlat: String(linha[IDX.pet_vet_lat] || ''), petvetlng: String(linha[IDX.pet_vet_lng] || '') }; } function atualizarDadosTAG(dados) { try { if (!dados.numeroLinha) { return { sucesso: false, erro: 'Linha não identificada.' }; } var n = parseInt(dados.numeroLinha, 10); var sheet = getCadastroSheet(); var linhaAtual = sheet.getRange(n, 1, 1, IDX.pet_vet_lng + 1).getValues()[0]; var emailLinha = normalizeEmail(linhaAtual[IDX.email]); var emailReq = normalizeEmail(dados.email || dados.emailRegistro || ''); if (emailReq && emailLinha !== emailReq) { return { sucesso: false, erro: 'Esta TAG não pertence a esta conta.' }; } var linkFoto = dados.fotoLink || ''; if (linkFoto && String(linkFoto).startsWith('data:image')) { linkFoto = uploadFotoParaDrive(linkFoto, dados.tag || linhaAtual[IDX.tag], dados.nome || linhaAtual[IDX.nome]); } sheet.getRange(n, IDX.timestamp + 1).setValue(new Date()); sheet.getRange(n, IDX.email + 1).setValue(dados.email || dados.emailRegistro || linhaAtual[IDX.email] || ''); sheet.getRange(n, IDX.tag + 1).setValue(dados.tag || linhaAtual[IDX.tag] || ''); sheet.getRange(n, IDX.nome + 1).setValue(dados.nome || ''); sheet.getRange(n, IDX.telefone + 1).setValue(dados.telefone || ''); sheet.getRange(n, IDX.dataNasc + 1).setValue(dados.dataNasc || ''); sheet.getRange(n, IDX.tipoSang + 1).setValue(dados.tipoSang || ''); sheet.getRange(n, IDX.doadorOrgaos + 1).setValue(dados.doadorOrgaos || ''); sheet.getRange(n, IDX.meds + 1).setValue(dados.meds || ''); sheet.getRange(n, IDX.doencas + 1).setValue(dados.doencas || ''); sheet.getRange(n, IDX.alergias + 1).setValue(dados.alergias || ''); sheet.getRange(n, IDX.plano + 1).setValue(dados.plano || ''); sheet.getRange(n, IDX.sus + 1).setValue(dados.sus || ''); sheet.getRange(n, IDX.cont1Nome + 1).setValue(dados.cont1Nome || ''); sheet.getRange(n, IDX.cont1Tel + 1).setValue(dados.cont1Tel || ''); sheet.getRange(n, IDX.cont2Nome + 1).setValue(dados.cont2Nome || ''); sheet.getRange(n, IDX.cont2Tel + 1).setValue(dados.cont2Tel || ''); sheet.getRange(n, IDX.obs + 1).setValue(dados.obs || ''); sheet.getRange(n, IDX.foto + 1).setValue(linkFoto || ''); sheet.getRange(n, IDX.apelido_tag + 1).setValue(dados.apelido || ''); sheet.getRange(n, IDX.categoria_tag + 1).setValue(normalizarCategoria(dados.categoria || 'emergencial')); sheet.getRange(n, IDX.status_tag + 1).setValue(normalizarStatus(dados.status || 'ATIVA')); var categoria = normalizarCategoria(dados.categoria || 'emergencial'); if (categoriaEhPet(categoria)) { sheet.getRange(n, IDX.pet_nome + 1).setValue(dados.petnome || ''); sheet.getRange(n, IDX.pet_especie + 1).setValue(dados.petespecie || ''); sheet.getRange(n, IDX.pet_raca + 1).setValue(dados.petraca || ''); sheet.getRange(n, IDX.pet_pelagem + 1).setValue(dados.petpelagem || ''); sheet.getRange(n, IDX.pet_sexo + 1).setValue(dados.petsexo || (categoria === 'pet_macho' ? 'Macho' : 'Fêmea')); sheet.getRange(n, IDX.pet_castrado + 1).setValue(dados.petcastrado || ''); sheet.getRange(n, IDX.pet_nascimento + 1).setValue(dados.petnascimento || ''); sheet.getRange(n, IDX.pet_vacinas + 1).setValue(dados.petvacinas || ''); sheet.getRange(n, IDX.pet_saude + 1).setValue(dados.petsaude || ''); sheet.getRange(n, IDX.pet_meds + 1).setValue(dados.petmeds || ''); sheet.getRange(n, IDX.pet_procedimentos + 1).setValue(dados.petprocedimentos || ''); sheet.getRange(n, IDX.pet_vet_nome + 1).setValue(dados.petvetnome || ''); sheet.getRange(n, IDX.pet_vet_tel + 1).setValue(dados.petvettel || ''); sheet.getRange(n, IDX.pet_vet_endereco + 1).setValue(dados.petvetendereco || ''); sheet.getRange(n, IDX.pet_localizacao + 1).setValue(dados.petlocalizacao || ''); sheet.getRange(n, IDX.pet_exames + 1).setValue(dados.petexames || ''); sheet.getRange(n, IDX.pet_obs + 1).setValue(dados.petobs || ''); sheet.getRange(n, IDX.tutor_cont1_nome + 1).setValue(dados.tutorcont1nome || ''); sheet.getRange(n, IDX.tutor_cont1_tel + 1).setValue(dados.tutorcont1tel || ''); sheet.getRange(n, IDX.tutor_cont2_nome + 1).setValue(dados.tutorcont2nome || ''); sheet.getRange(n, IDX.tutor_cont2_tel + 1).setValue(dados.tutorcont2tel || ''); sheet.getRange(n, IDX.pet_vet_lat + 1).setValue(dados.petvetlat || ''); sheet.getRange(n, IDX.pet_vet_lng + 1).setValue(dados.petvetlng || ''); } else { sheet.getRange(n, IDX.pet_nome + 1, 1, (IDX.pet_vet_lng - IDX.pet_nome + 1)).clearContent(); } return { sucesso: true, mensagem: 'Dados atualizados com sucesso.' }; } catch (e) { return { sucesso: false, erro: e.toString() }; } }